%%%-------------------------------------------------------------------
%%% File    : hiberl_session.erl
%%% Author  : alfonso <alfonso@alfonso-laptop>
%%% Description : 
%%%
%%% Created : 17 Sep 2009 by alfonso <alfonso@alfonso-laptop>
%%%-------------------------------------------------------------------
-module(hiberl_session).

-behaviour(gen_server).

%% API
-export([start_link/0, 
	 start/1, start/2, 
	 start_register/1,  start_register/2,
	 terminate/0, terminate/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-export([get/4, 
	 pretty_print/1, 
	 get_data/1, 
	 execute_sql/2,
	 tx/2, tx/1,
	 commit/1, commit/0]).

-record(state, {
	  url,
	  odbc, 
	  pids = []
	 }).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

start(Url) ->
    start(Url,[]).

start(Url, Opts) ->
    gen_server:start_link( ?MODULE, [Url, Opts], []).

start_register(Url) ->
    start_register(Url,[]).

start_register(Url,Opts) ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [Url, Opts], []).

get(Module, Table, Where, Order)->
    gen_server:call(?MODULE, {get, Module, Table, Where, Order}).

pretty_print(Module)->
    gen_server:call(?MODULE, {pretty_print, Module}).

get_data(Module)->
    gen_server:call(?MODULE, {get_data, Module}).

terminate()->
    terminate(?MODULE).

terminate(Pid)->
    gen_server:call(Pid, terminate).

execute_sql(Pid,S)->
    gen_server:call(Pid, {execute_sql,S}).   

tx(S)->
    tx(?MODULE, S).   

tx(Pid,S)->
    gen_server:call(Pid, {tx,S}, infinity).   

commit()->
    commit(?MODULE).
commit(Pid) ->
    gen_server:call(Pid, commit).   
    
%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Url, OdbcOpts]) ->
    odbc:start(),
    process_flag(trap_exit, true),
    {ok, Ref} = odbc:connect(Url, [{trace_driver, on}] ++ [{{scrollable_cursors, off}} | OdbcOpts]),
    {ok, #state{odbc = Ref, url = Url}}.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({get, Module, Table, Where, Order}, _From, State) ->
    SQL = ["SELECT * FROM ", Table, " WHERE ",Where," ORDER BY ", Order],
    {selected, Columns, Data} = odbc:sql_query(State#state.odbc, SQL),
    F = fun(Row)->
		Module:init( self(),
			     lists:zip( 
			       lists:map(fun hiberl_utils:low_first/1, Columns), 
			       tuple_to_list(Row) ))
	end,
    {reply, lists:map(F,Data),State};

handle_call({pretty_print, Module}, _From, State) ->    
    PP = [ Module:pretty_print(Pid) || Pid <- State#state.pids],
    {reply, [Module:columns() ++ "\n"| PP] ,State};

handle_call({tx, Rs}, _From, State)->
    {ok,Ref} = hiberl_session:start(State#state.url, [{auto_commit, off}]),
    F = fun(R) ->
		Module = element(1,R),
		Module:commit( Module:set_session(R, Ref)  )
	end,
    lists:map(F, Rs),
    Ok = hiberl_session:commit(Ref),
    hiberl_session:terminate(Ref),
    {reply, Ok ,State};

handle_call(commit, _From, State)->
    Ok = (catch odbc:commit(State#state.odbc, commit)),
    {reply, Ok ,State};

handle_call({get_data,Module}, _From, State) ->    
    {reply, [ Module:columns() | [ gen_server:call(Pid,get_data) || Pid <- State#state.pids] ] ,State};

handle_call({execute_sql,S}, _From, State) ->  
%    io:format("sql: ~s ~n",[S]),
    X = odbc:sql_query(State#state.odbc, S),
    {reply, X, State};
    
handle_call(terminate, _From, State) ->
    {stop, normal, ok, State};

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
    [gen_server:call(Pid, terminate) || Pid <- State#state.pids],
    odbc:disconnect(State#state.odbc),
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
